# Copyright (c) 2008 Todd A. Fisher
# see LICENSE
require 'uri'
require 'net/http'
require 'esi/tag_handler'

module ESI
  module Tag

    #
    #
    #  ir = IncludeRequest.new( {'header1'=>'value1'} )
    #
    #  ir.request( '/fragment' ) do|status,response|
    #     if status 
    #         response.read_body do|str|
    #         end
    #     else
    #         # error case
    #     end
    #  end
    #
    class IncludeRequest
      class Error
        attr_reader :message, :response
        def initialize(msg,response)
          @message = msg
          @response = response
        end
      end
      attr_reader :exception, :overflow_index # TODO

      def initialize(forward_headers)
        @headers = forward_headers
      end

      def request(uri, timeout = 1, alt_failover=nil, follow_limit=3)
        uri = URI.parse(uri) if uri.is_a?(String)
        http = Net::HTTP.new(uri.host, uri.port)
        http.read_timeout = timeout
        http.get2( uri.to_s, @headers ) do|response|
          case response
          when Net::HTTPSuccess
            yield true, response, uri
          when Net::HTTPRedirection
            ir = IncludeRequest.new(@headers)
            ir.request(URI.parse(response['location']), timeout, alt_failover, follow_limit - 1) do|s,r|
              yield s, r, URI.parse(response['location'])
            end
          else
            if alt_failover
              ir = IncludeRequest.new(@headers)
              ir.request(alt_failover, timeout, nil, follow_limit) do|s,r|
                yield s, r, URI.parse(alt_failover)
              end
            else
              yield false, Error.new("Failed to request fragment: #{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}", response), uri
            end
          end
        end
      rescue Timeout::Error => e
        yield false, Error.new("Failed to request fragment: #{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}, timeout error: #{e.message}", nil), uri
      end

    end

    class Include < Base
      include ESI::TagHandler

      attr_accessor :depth, :max_depth
      def initialize(uri,headers,http_params,name,attrs,cache)
        super
        @depth = 0
        @max_depth = 3
      end

      def parse_fragment?
        @depth <= @max_depth
      end

      def close( output, options = {} )
        super(output)

        raise_on_error = options[:raise] || false
        ir = IncludeRequest.new(@http_params)

        src = @router.url_for(prepare_url_vars(@attributes["src"]))
        alt = @attributes['alt']
        alt = @router.url_for(prepare_url_vars(alt)) if alt

        parser = nil

        if parse_fragment?
          parser = ESI::CParser.new
          parser.output = output
          parser.depth = (@depth+1)

          # NOTE: really bad things happen if we attempt to copy the closure from the main parser
          # in esi/handler.rb

          # handle start tags
          start_tag_handler( parser, @router, @cache, @headers, @http_params, @max_depth )
 
          # handle end tags
          end_tag_handler( parser )

        end


        if @cache.cached?( src, @http_params )
          cached_fragment = @cache.get( src, @headers ).body
          log_request "C"
          if parse_fragment?

            parser.process cached_fragment
            parser.finish

          else
            output << cached_fragment
          end
 
        else

          ir.request(src, @attributes['timeout'].to_i, alt ) do|status,response,uri|
            if status
              # NOTE: it's important that we cache the unprocessed markup, because we need to 
              # reprocess the esi:include vars even for cached content, this way we can have cached content
              # with HTTP_COOKIE vars and avoid re-requesting content
              log_request "R"
              cache_buffer = ""
              response.read_body do|s|
                cache_buffer << s
                if parse_fragment?
                  parser.process s
                else
                  output << s
                end
              end
 
              parser.finish if parse_fragment?

              if src != uri # these won't be equal if the fragment followed a redirect or used the alt condition
                if uri.query
                  request_uri = "#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}?#{uri.query}"
                else
                  request_uri =  "#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}"
                end
              else
                request_uri = src
              end

              @cache.put(request_uri, @http_params, 600, cache_buffer )

            else
              # error/ check if the include has an onerror specifier
              return if @attributes['onerror'] == 'continue'
              # response is an IncludeRequest::Error
              raise response.message if raise_on_error
              # stop processing and return the error object
              return response
            end
          end
        end

        parser = nil

      end
    end

  end
end
